Skip to content

Conversation

@charmitro
Copy link
Contributor

When using 'cargo update -Zunstable-options --breaking ', package specifications that don't match any dependency in the workspace or lockfile are silently ignored instead of reporting an error. This leads to confusing behavior where users think they're updating a package that doesn't exist.

Fix this by tracking which specs match dependencies during the upgrade process. After processing all workspace members, verify that each requested spec matched at least one direct dependency or exists in the lockfile as a transitive dependency. Report an error for any spec that matches neither.

The fix preserves existing behavior for renamed dependencies and non-registry sources while ensuring proper error reporting for genuinely missing packages.

Closes #16258

When using 'cargo update -Zunstable-options --breaking <spec>',
package specifications that don't match any dependency in the
workspace or lockfile are silently ignored instead of reporting an
error. This leads to confusing behavior where users think they're
updating a package that doesn't exist.

Fix this by tracking which specs match dependencies during the upgrade
process. After processing all workspace members, verify that each
requested spec matched at least one direct dependency or exists in the
lockfile as a transitive dependency. Report an error for any spec that
matches neither.

The fix preserves existing behavior for renamed dependencies and
non-registry sources while ensuring proper error reporting for
genuinely missing packages.

Signed-off-by: Charalampos Mitrodimas <[email protected]>
@rustbot rustbot added Command-update S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 19, 2025
@rustbot
Copy link
Collaborator

rustbot commented Nov 19, 2025

r? @epage

rustbot has assigned @epage.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Comment on lines +2754 to +2755
#[cargo_test]
fn update_breaking_missing_package_error() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something our contrib docs encourage is adding tests in the commit before with them passing, showing the old behavior. Then in this commit the test then gets updated to show the new behavior and the diff between them shows how the behavior changed. This does a good job communicating what a PR is doing (and tests the tests)

Comment on lines +271 to +279
// Spec didn't match any direct dependencies
// Check if it matches any package in the lockfile (including transitive deps)
let matches_lockfile = if let Some(ref resolve) = previous_resolve {
spec.query(resolve.iter()).is_ok()
} else {
false
};

if !matches_lockfile {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we skipping these? They look to be unused otherwise If we do error, we could leverage the logic you wrote to help inform the user that it does exist but that it isn't selectable

Comment on lines +269 to +270
for spec in &to_update {
if !matched_specs.contains(spec) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we report these all at once rather than just the first?

let name = dependency.package_name();
let renamed_to = dependency.name_in_toml();

if !to_update.is_empty() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this check present? If its empty, the loop just won't iterate

Comment on lines +320 to +322
if !to_update.is_empty() {
// Check if this dependency should be upgraded based on the specs
let should_upgrade = to_update.iter().any(|spec| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this changed?

Comment on lines +306 to +313
// Check if any spec matches this dependency by name
for spec in to_update.iter() {
if spec.name() == name.as_str() {
// Mark this spec as matched (exists in workspace)
matched_specs.insert(spec.clone());
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this here and not at the very bottom of the function when we actually make the change? This ends up duplicating the to_update check and considers it matched even when we won't upgrade it.

Comment on lines +264 to +270
// Check if any specs were not matched against direct dependencies
if !to_update.is_empty() {
// Load the lockfile to check for transitive dependencies
let previous_resolve = ops::load_pkg_lockfile(ws)?;

for spec in &to_update {
if !matched_specs.contains(spec) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this could be made simpler by replacing matched_specs with remaining_specs = to_update.clone(); (maybe switched to an IndexSet) and removing entries as we process them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Command-update S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing "no_such_crate did not match any packages" after cargo update --breaking

3 participants